package com.cgnpc.scp.common.service;

import org.apache.ibatis.mapping.*;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class ExcelTemplateService {
    private static final Logger LOGGER = LoggerFactory.getLogger(ExcelTemplateService.class);

    /**
     * 文件临时生成部分目录
     */
    private static final String TEMP_CREATE_PATH = "___temp_file/_download";

    private static final ConcurrentHashMap<String, String> LOCK_MAPPEDSTATEMENTID = new ConcurrentHashMap<>();

    public static void setSqlSession(
            String mappedStatementUsedId,
            SqlSession sqlSessionMethodPrivate,
            String mybatisSqlStatement
    ) throws Exception {
        // 数据太多，一次性查询的时候会内存溢出，现在设置成分批查询
        // 不修改configuration.setDefaultFetchSize()，
        // 设置mappedStatement.setFetchSize(Integer.MIN_VALUE)，因为源码中会优先使用mappedStatement.getFetchSize()；

        // 关于设置值问题，详见mysql官网文档：https://downloads.mysql.com/docs/connector-j-5.1-en.pdf
        // To enable this functionality, create a Statement instance in the following manner:
        // stmt = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY);
        // stmt.setFetchSize(Integer.MIN_VALUE);
        // The combination of a forward-only, read-only result set,
        // with a fetch size of Integer.MIN_VALUEserves as a signal to the driver to stream result sets row-by-row.
        // After this, any result sets created withthe statement will be retrieved row-by-row

        // Configuration configuration = sqlSessionMethodPrivate.getConfiguration();
        // DefaultFetchSize:当前使用了mysql8数据库，设置成3000会抛出异常Communications link failure
        // configuration.setDefaultFetchSize(3000);
        // setDefaultFetchSize设置成-1，则抛出异常Caused by: java.sql.SQLException: Illegal value for setFetchSize().
        // configuration.setDefaultFetchSize(-1);
        // setDefaultFetchSize:当前使用了mysql8数据库，设置成Integer.MIN_VALUE则能正常查询
        // configuration.setDefaultFetchSize(Integer.MIN_VALUE);

        // 本处不改变Configuration的值，只改变MappedStatement的值

        // 一般来说，导出excel可以使用前缀myExport。
        // String newMappedStatementId = "myExport." + mybatisSqlStatement;
        String newMappedStatementId = mappedStatementUsedId;

        // java.sql.Connection connection = sqlSessionMethodPrivate.getConnection(); // 这里居然获取到一个关闭的连接
        Configuration configuration = sqlSessionMethodPrivate.getConfiguration();

        // begin 动态创建类似游标的方式来设置缓冲区MappedStatement
        createMappedStatementWithNew(mybatisSqlStatement, configuration, newMappedStatementId);
        // end 动态创建类似游标的方式来设置缓冲区MappedStatement

        MappedStatement mappedStatementUsed = configuration.getMappedStatement(newMappedStatementId);

        Field fieldResultSetType = MappedStatement.class.getDeclaredField("resultSetType");
        Field fieldFetchSize = MappedStatement.class.getDeclaredField("fetchSize");

       // fieldResultSetType.setAccessible(true);
        ReflectionUtils.makeAccessible(fieldResultSetType);
        fieldResultSetType.set(mappedStatementUsed, ResultSetType.FORWARD_ONLY);

        //fieldFetchSize.setAccessible(true);
        ReflectionUtils.makeAccessible(fieldFetchSize);
        fieldFetchSize.set(mappedStatementUsed, Integer.MIN_VALUE);
    }

    /**
     * 创建全新的MappedStatement
     *
     * @param mybatisSqlStatementIdSrc mybatisSqlStatementIdSrc
     * @param configuration            configuration
     * @param newMappedStatementId     newMappedStatementId
     */
    private static void createMappedStatementWithNew(
            String mybatisSqlStatementIdSrc,
            Configuration configuration,
            String newMappedStatementId
    ) {

        Boolean ismappedStatement = false;
        if (configuration.hasStatement(newMappedStatementId)) {
            ismappedStatement=true;
        }
        // begin 动态创建类似游标的方式来设置缓冲区MappedStatement
        if (!ismappedStatement) {
            LOCK_MAPPEDSTATEMENTID.putIfAbsent(newMappedStatementId, newMappedStatementId);
            String lockNewMappedStatementId = LOCK_MAPPEDSTATEMENTID.get(newMappedStatementId);

            synchronized (lockNewMappedStatementId) {
                if (!configuration.hasStatement(newMappedStatementId)) {
                    MappedStatement mappedStatementSrc = configuration.getMappedStatement(mybatisSqlStatementIdSrc);

                    SqlSource mappedStatementSrcSqlSource = mappedStatementSrc.getSqlSource();
                    SqlCommandType mappedStatementSrcSqlCommandType = mappedStatementSrc.getSqlCommandType();
                    Integer mappedStatementSrcFetchSize = mappedStatementSrc.getFetchSize();
                    ResultSetType mappedStatementSrcResultSetType = mappedStatementSrc.getResultSetType();
                    List<ResultMap> mappedStatementSrcResultMaps = mappedStatementSrc.getResultMaps();
                    boolean mappedStatementSrcResultOrdered = mappedStatementSrc.isResultOrdered();

                    MappedStatement.Builder builder = new MappedStatement.Builder(
                            configuration,
                            newMappedStatementId,
                            mappedStatementSrcSqlSource,
                            mappedStatementSrcSqlCommandType
                    );

                    builder.resultMaps(mappedStatementSrcResultMaps);
                    builder.fetchSize(mappedStatementSrcFetchSize);
                    builder.resultOrdered(mappedStatementSrcResultOrdered);
                    builder.resultSetType(mappedStatementSrcResultSetType);

                    // 如果在原有的MappedStatement对象中设置了值，有可能会影响以前的查询，现在重新new一个新对象
                    MappedStatement cursorMappedStatement = builder.build();
                    // 添加一个新的MappedStatement
                    configuration.addMappedStatement(cursorMappedStatement);
                }
            }

            // 可以在本处删除锁key，按照设计来说代码到了这里configuration.hasStatement(newMappedStatementId)的返回值为true
            LOCK_MAPPEDSTATEMENTID.remove(lockNewMappedStatementId);
        }
        // end 动态创建类似游标的方式来设置缓冲区MappedStatement
    }



}
